﻿
using System;
using System.Collections.Generic;
using System.Linq;
namespace PSPROOFUtils
{
    /// <summary>
    /// A cache that helps with keeping track of datasets on the server. The datasets
    /// are powershell items.
    /// </summary>
    class DSCache
    {
        /// <summary>
        /// The time of the last cache update.
        /// </summary>
        private DateTime _lastCacheUpdate;

        /// <summary>
        /// The max age our cache is allowed to get to.
        /// </summary>
        private TimeSpan _maxCacheAge = new TimeSpan(0, 5, 0);

        /// <summary>
        /// Track the actual cache
        /// </summary>
        private Dictionary<string, ProofDataSetItem> _cache = new Dictionary<string, ProofDataSetItem>();

        /// <summary>
        /// How many times should we attempt to get the dataset list before giving up and throwing?
        /// </summary>
        private int _maxDatasetRetries = 10;

        /// <summary>
        /// The proof connection.
        /// </summary>
        public ROOTNET.Interface.NTProof ProofConnection { get; set; }

        /// <summary>
        /// Update the internal cache, if we need to...
        /// </summary>
        /// <param name="ProofConnection">This is heald onto for teh rest of the lifetime of the cache</param>
        internal void Update()
        {
            if (ProofConnection == null)
                throw new InvalidOperationException("Can't try to update the proof cache if the connection to proof hasn't been established");

            //
            // Is it time?
            //

            if ((DateTime.Now - _lastCacheUpdate) < _maxCacheAge)
                return;

            //
            // Save all local datasets - they won't have been updated remotely yet.
            //

            var localDS = _cache.Where(ds => ds.Value.IsLocal).Select(ds => ds.Value).ToArray();

            //
            // Get a listing of all the datasets
            //

            _cache.Clear();
            var proofDS = LoadProofDSList(ProofConnection);
            foreach (var dsname in proofDS.Cast<ROOTNET.Interface.NTObjString>())
            {
                //
                // Ignore namespaces for now ("/users/gwatts/default/HG123");
                //

                var name = dsname.Name.Split('/').Last();

                //
                // Fetch back the global metadata
                //

                var fc = proofDS.GetValue(dsname) as ROOTNET.Interface.NTFileCollection;

                //
                // This guy brings back only global meta-data information, so only store that!
                //

                var ds = new ProofDataSetItem(name, fc.NFiles, fc.NCorruptFiles, fc.NStagedFiles, fc.TotalSize);
                AddDataset(ds);
            }

            //
            // And add in the local datasets we saved above.
            //

            foreach (var ds in localDS)
            {
                AddDataset(ds);
            }

            //
            // Rest the cache time
            //

            _lastCacheUpdate = DateTime.Now;
        }

        /// <summary>
        /// Sometimes proof seems to have some trouble getting us back a dataset list... so
        /// keep trying...
        /// </summary>
        /// <param name="proof"></param>
        /// <returns></returns>
        private ROOTNET.Interface.NTMap LoadProofDSList(ROOTNET.Interface.NTProof proof)
        {
            for (int i = 0; i < _maxDatasetRetries; i++)
            {
                var r = proof.DataSets;
                if (r != null)
                    return r;
                System.Threading.Thread.Sleep(100);
            }

            throw new InvalidOperationException("Unable to get the dataset list from the proof server");
        }

        /// <summary>
        /// Return true if the dataset exists on this proof server.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        internal bool HasDataset(string path)
        {
            return _cache.ContainsKey(path);
        }

        /// <summary>
        /// Return a list of the proof datasets.
        /// </summary>
        /// <returns></returns>
        internal IEnumerable<ProofDataSetItem> GetDSItems()
        {
            return _cache.Values;
        }

        /// <summary>
        /// Return an item in our cache, or throw.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        /// <remarks>
        /// This cache could be local only, in which case we will fail to get the dataset... so avoid it.
        /// </remarks>
        internal ProofDataSetItem GetDSItem(string path, bool fullInformation)
        {
            var item = _cache[path];

            if (fullInformation && !item.InformationComplete && !item.IsLocal)
            {
                var ds = RetryGetDataSet(item.Name);
                var files = ds.List.Cast<ROOTNET.Interface.NTFileInfo>();
                item.SetFullData(files);
            }

            return item;
        }

        /// <summary>
        /// Gets the dataset, and if null comes back, does a retry. Only do this if
        /// you are sure the dataset exists on the server, otherwise this is a bit of
        /// a waste of time.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        internal ROOTNET.Interface.NTFileCollection RetryGetDataSet(string name)
        {
            for (int i = 0; i < _maxDatasetRetries; i++)
            {
                try
                {
                    var ds = ProofConnection.GetDataSet(name);
                    if (ds != null)
                        return ds;
                }
                catch
                { }
            }

            throw new InvalidOperationException(string.Format("I am pretty sure the dataset '{0}' exists on the server, but I can't get PROOF to return details about it!", name));
        }

        /// <summary>
        /// A dataset is being added to the cache.
        /// </summary>
        /// <param name="ds"></param>
        internal void AddDataset(ProofDataSetItem ds)
        {
            _cache[ds.Name] = ds;
            ds.ProofConnection = ProofConnection;
        }

        /// <summary>
        /// Remove this item from the map.
        /// </summary>
        /// <param name="ds"></param>
        internal void Remove(ProofDataSetItem ds)
        {
            _cache.Remove(ds.Name);
        }
    }
}
